home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / hplip / scan.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  44.0 KB  |  1,170 lines

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  19. #
  20. # Author: Don Welch
  21. #
  22.  
  23. from __future__ import division
  24.  
  25. __version__ = '2.1'
  26. __mod__ = 'hp-scan'
  27. __title__ = 'Scan Utility'
  28. __doc__ = "SANE-based scan utility for HPLIP supported all-in-one/mfp devices (EXPERIMENTAL)."
  29.  
  30. # Std Lib
  31. import sys
  32. import os
  33. import os.path
  34. import getopt
  35. import signal
  36. import time
  37. import socket
  38. import operator
  39.  
  40. # Local
  41. from base.g import *
  42. from base import tui, device, module, utils
  43. from prnt import cups
  44.  
  45.  
  46. username = prop.username
  47. res = 300
  48. scan_mode = 'color'
  49. tlx = None
  50. tly = None
  51. brx = None
  52. bry = None
  53. units = "mm"
  54. output = ''
  55. dest = []
  56. email_from = ''
  57. email_to = []
  58. email_subject = 'hp-scan from %s' % socket.gethostname()
  59. email_note = ''
  60. fax = ''
  61. resize = 100
  62. contrast = 0
  63. brightness = 0
  64. #printer = ''
  65. page_size = ''
  66. size_desc = ''
  67. page_units = 'mm'
  68. valid_res = (75, 150, 300, 600, 1200, 2400, 4800)
  69. default_res = 300
  70. scanner_compression = 'JPEG'
  71. adf = False
  72.  
  73. PAGE_SIZES = { # in mm
  74.     '5x7' : (127, 178, "5x7 photo", 'in'),
  75.     '4x6' : (102, 152, "4x6 photo", 'in'),
  76.     '3x5' : (76, 127, "3x5 index card", 'in'),
  77.     'a2_env' : (111, 146, "A2 Envelope", 'in'),
  78.     'a3' : (297, 420, "A3", 'mm'),
  79.     "a4" : (210, 297, "A4", 'mm'),
  80.     "a5" : (148, 210, "A5", 'mm'),
  81.     "a6" : (105, 148, "A6", 'mm'),
  82.     "b4" : (257, 364, "B4", 'mm'),
  83.     "b5" : (182, 257, "B5", 'mm'),
  84.     "c6_env" : (114, 162, "C6 Envelope", 'in'),
  85.     "dl_env" : (110, 220, "DL Envelope", 'in'),
  86.     "exec" : (184, 267, "Executive", 'in'),
  87.     "flsa" : (216, 330, "Flsa", 'mm'),
  88.     "higaki" : (100, 148, "Hagaki", 'mm'),
  89.     "japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'),
  90.     "japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'),
  91.     "legal" : (215, 356, "Legal", 'in'),
  92.     "letter" : (215, 279, "Letter", 'in'),
  93.     "no_10_env" : (105, 241, "Number 10 Envelope", 'in'),
  94.     "oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'),
  95.     "photo" : (102, 152, "Photo", 'in'),
  96.     "super_b" : (330, 483, "Super B", 'in'),
  97.     }
  98.  
  99.  
  100. try:
  101.     viewer = ''
  102.     viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',]
  103.     for v in viewer_list:
  104.         vv = utils.which(v)
  105.         if vv:
  106.             viewer = os.path.join(vv, v)
  107.             break
  108.  
  109.  
  110.     editor = ''
  111.     editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',]
  112.     for e in editor_list:
  113.         ee = utils.which(e)
  114.         if ee:
  115.             editor = os.path.join(ee, e)
  116.             break
  117.  
  118.     pdf_viewer = ''
  119.     pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',]
  120.     for v in pdf_viewer_list:
  121.         vv = utils.which(v)
  122.         if vv:
  123.             pdf_viewer = os.path.join(vv, v)
  124.             break
  125.  
  126.     mod = module.Module(__mod__, __title__, __version__, __doc__, None,
  127.                         (NON_INTERACTIVE_MODE,))
  128.  
  129.     mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS,
  130.         extra_options=[utils.USAGE_SPACE,
  131.         ("[OPTIONS] (General)", "", "header", False),
  132.         ("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False),
  133.         ("", "where <dest_list> is a comma separated list containing one or more of: 'file'\*, ", "option", False),
  134.         ("", "'viewer', 'editor', 'pdf', 'fax', or 'print'. Use only commas between values, no spaces.", "option", False),
  135.         ("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'color'\*, 'gray' or 'lineart'.", "option", False),
  136.         ("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False),
  137.         ("", "where <resolution_in_dpi> is %s (300 is default)." % ', '.join([str(x) for x in valid_res]), "option", False),
  138.         ("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False),
  139.         ("Image contrast:", "--contrast=<contrast>", "option", False),
  140.         ("ADF mode (EXPERIMENTAL):", "--adf (Note, only PDF output is supported when using the ADF)", "option", False),
  141.         utils.USAGE_SPACE,
  142.         ("[OPTIONS] (Scan area)", "", "header", False),
  143.         ("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False),
  144.         ("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False),
  145.         ("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False),
  146.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  147.         ("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False),
  148.         ("", "Use only commas between values, no spaces.", "option", False),
  149.         ("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False),
  150.         ("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False),
  151.         ("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False),
  152.         ("", "Use only commas between values, no spaces.", "option", False),
  153.         ("Top left x of the scan area:", "--tlx=<tlx>", "option", False),
  154.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  155.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  156.         ("Top left y of the scan area:", "--tly=<tly>", "option", False),
  157.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  158.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  159.         ("Bottom right x of the scan area:", "--brx=<brx>", "option", False),
  160.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  161.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  162.         ("Bottom right y   of the scan area:", "--bry=<bry>", "option", False),
  163.         ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False),
  164.         ("", "Units are specified by -t/--units (default is 'mm').", "option", False),
  165.         ("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False),
  166.         ("", "where <paper size name> is one of: %s" % ', '.join(PAGE_SIZES.keys()), "option", False),
  167.         utils.USAGE_SPACE,
  168.         ("[OPTIONS] ('file' dest)", "", "header", False),
  169.         ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False),
  170.         utils.USAGE_SPACE,
  171.         ("[OPTIONS] ('pdf' dest)", "", "header", False),
  172.         ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False),
  173.         utils.USAGE_SPACE,
  174.         ("[OPTIONS] ('viewer' dest)", "", "header", False),
  175.         ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False),
  176.         utils.USAGE_SPACE,
  177.         ("[OPTIONS] ('editor' dest)", "", "header", False),
  178.         ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False),
  179.         utils.USAGE_SPACE,
  180.         ("[OPTIONS] ('email' dest)", "", "header", False),
  181.         ("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False),
  182.         ("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False),
  183.         ("Email subject for 'email' dest:", '--email-subject="<subject>" or --subject="<subject>"', "option", False),
  184.         ("", 'Use double quotes (") around the subject if it contains space characters.', "option", False),
  185.         ("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False),
  186.         ("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False),
  187.         utils.USAGE_SPACE,
  188.         ("[OPTIONS] ('fax' dest)", "", "header", False),
  189.         ("Fax queue/printer:", "--fax=<fax_printer_name>", "option", False),
  190.         utils.USAGE_SPACE,
  191.         ("[OPTIONS] ('printer' dest)", "", "header", False),
  192.         ("Printer queue/printer:", "--printer=<printer_name>", "option", False),
  193.         utils.USAGE_SPACE,
  194.         ("[OPTIONS] (advanced)", "", "header", False),
  195.         ("Set the scanner compression mode:", "-x<mode> or --compression=<mode>, <mode>='raw', 'none' or 'jpeg' ('jpeg' is default) ('raw' and 'none' are equivalent)", "option", False),],
  196.         see_also_list=[])
  197.  
  198.     opts, device_uri, printer_name, mode, ui_toolkit, lang = \
  199.         mod.parseStdOpts('s:m:r:c:t:a:b:o:v:f:c:x:',
  200.                          ['dest=', 'mode=', 'res=', 'resolution=',
  201.                           'resize=', 'contrast=', 'adf', 'unit=',
  202.                           'units=', 'area=', 'box=', 'tlx=',
  203.                           'tly=', 'brx=', 'bry=', 'size=',
  204.                           'file=', 'output=', 'pdf=', 'viewer=',
  205.                           'email-from=', 'from=', 'email-to=',
  206.                           'to=', 'email-msg=', 'msg=', 'fax=',
  207.                           'printer=', 'compression=' , 'raw',
  208.                           'jpeg', 'color', 'lineart', 'colour',
  209.                           'bw', 'gray', 'grayscale', 'grey',
  210.                           'greyscale', 'email-subject=',
  211.                           'subject=', 'to=', 'from=', 'jpg',
  212.                           'grey-scale', 'gray-scale', 'about=',
  213.                          ])
  214.  
  215.  
  216.  
  217.     device_uri = mod.getDeviceUri(device_uri, printer_name,
  218.         back_end_filter=['hpaio'], filter={'scan-type': (operator.gt, 0)})
  219.  
  220.     for o, a in opts:
  221.         if o in ('-x', '--compression'):
  222.             a = a.strip().lower()
  223.  
  224.             if a in ('jpeg', 'jpg'):
  225.                 scanner_compression = 'JPEG'
  226.  
  227.             elif a in ('raw', 'none'):
  228.                 scanner_compression = 'None'
  229.  
  230.             else:
  231.                 log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.")
  232.                 log.error("Using default value of 'jpeg'.")
  233.                 scanner_compression = 'JPEG'
  234.  
  235.         elif o == 'raw':
  236.             scanner_compression = 'None'
  237.  
  238.         elif o == 'jpeg':
  239.             scanner_compression = 'JPEG'
  240.  
  241.         elif o in ('--color', '--colour'):
  242.             scan_mode = 'color'
  243.  
  244.         elif o in ('--lineart', '--line-art', '--bw'):
  245.             scan_mode = 'lineart'
  246.  
  247.         elif o in ('--gray', '--grayscale', '--gray-scale', '--grey', '--greyscale', '--grey-scale'):
  248.             scan_mode = 'gray'
  249.  
  250.         elif o in ('-m', '--mode'):
  251.             a = a.strip().lower()
  252.  
  253.             if a in ('color', 'colour'):
  254.                 scan_mode = 'color'
  255.  
  256.             elif a in ('lineart', 'bw', 'b&w'):
  257.                 scan_mode = 'lineart'
  258.  
  259.             elif a in ('gray', 'grayscale', 'grey', 'greyscale'):
  260.                 scan_mode = 'gray'
  261.  
  262.             else:
  263.                 log.error("Invalid mode. Using default of 'color'.")
  264.                 log.error("Valid modes are 'color', 'lineart', or 'gray'.")
  265.                 scan_mode = 'color'
  266.  
  267.         elif o in ('--res', '--resolution', '-r'):
  268.             try:
  269.                 r = int(a.strip())
  270.             except ValueError:
  271.                 log.error("Invalid resolution. Using default of %s dpi." % default_res)
  272.                 log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
  273.                 res = default_res
  274.             else:
  275.                 if r in valid_res:
  276.                     res = r
  277.                 else:
  278.                     res = valid_res[0]
  279.                     min_dist = sys.maxint
  280.                     for x in valid_res:
  281.                         if abs(r-x) < min_dist:
  282.                             min_dist = abs(r-x)
  283.                             res = x
  284.  
  285.                     log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res)
  286.                     log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res]))
  287.  
  288.         elif o in ('-t', '--units', '--unit'):
  289.             a = a.strip().lower()
  290.  
  291.             if a in ('in', 'inch', 'inches'):
  292.                 units = 'in'
  293.  
  294.             elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'):
  295.                 units = 'mm'
  296.  
  297.             elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'):
  298.                 units = 'cm'
  299.  
  300.             elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'):
  301.                 units = 'px'
  302.  
  303.             elif a in ('pt', 'point', 'points', 'pts'):
  304.                 units = 'pt'
  305.  
  306.             else:
  307.                 log.error("Invalid units. Using default of 'mm'.")
  308.                 units = 'mm'
  309.  
  310.         elif o == '--tlx':
  311.             a = a.strip().lower()
  312.             try:
  313.                 f = float(a)
  314.             except ValueError:
  315.                 log.error("Invalid value for tlx.")
  316.             else:
  317.                 tlx = f
  318.  
  319.         elif o == '--tly':
  320.             a = a.strip().lower()
  321.             try:
  322.                 f = float(a)
  323.             except ValueError:
  324.                 log.error("Invalid value for tly.")
  325.             else:
  326.                 tly = f
  327.  
  328.         elif o == '--brx':
  329.             a = a.strip().lower()
  330.             try:
  331.                 f = float(a)
  332.             except ValueError:
  333.                 log.error("Invalid value for brx.")
  334.             else:
  335.                 brx = f
  336.  
  337.         elif o == '--bry':
  338.             a = a.strip().lower()
  339.             try:
  340.                 f = float(a)
  341.             except ValueError:
  342.                 log.error("Invalid value for bry.")
  343.             else:
  344.                 bry = f
  345.  
  346.         elif o in ('-a', '--area'): # tlx, tly, brx, bry
  347.             a = a.strip().lower()
  348.             try:
  349.                 tlx, tly, brx, bry = a.split(',')[:4]
  350.             except ValueError:
  351.                 log.error("Invalid scan area. Using defaults.")
  352.             else:
  353.                 try:
  354.                     tlx = float(tlx)
  355.                 except ValueError:
  356.                     log.error("Invalid value for tlx. Using defaults.")
  357.                     tlx = None
  358.  
  359.                 try:
  360.                     tly = float(tly)
  361.                 except ValueError:
  362.                     log.error("Invalid value for tly. Using defaults.")
  363.                     tly = None
  364.  
  365.                 try:
  366.                     brx = float(brx)
  367.                 except ValueError:
  368.                     log.error("Invalid value for brx. Using defaults.")
  369.                     brx = None
  370.  
  371.                 try:
  372.                     bry = float(bry)
  373.                 except ValueError:
  374.                     log.error("Invalid value for bry. Using defaults.")
  375.                     bry = None
  376.  
  377.         elif o in ('-b', '--box'): # tlx, tly, w, h
  378.             a = a.strip().lower()
  379.             try:
  380.                 tlx, tly, width, height = a.split(',')[:4]
  381.             except ValueError:
  382.                 log.error("Invalid scan area. Using defaults.")
  383.             else:
  384.                 try:
  385.                     tlx = float(tlx)
  386.                 except ValueError:
  387.                     log.error("Invalid value for tlx. Using defaults.")
  388.                     tlx = None
  389.  
  390.                 try:
  391.                     tly = float(tly)
  392.                 except ValueError:
  393.                     log.error("Invalid value for tly. Using defaults.")
  394.                     tly = None
  395.  
  396.                 if tlx is not None:
  397.                     try:
  398.                         brx = float(width) + tlx
  399.                     except ValueError:
  400.                         log.error("Invalid value for width. Using defaults.")
  401.                         brx = None
  402.                 else:
  403.                     log.error("Cannot calculate brx since tlx is invalid. Using defaults.")
  404.                     brx = None
  405.  
  406.                 if tly is not None:
  407.                     try:
  408.                         bry = float(height) + tly
  409.                     except ValueError:
  410.                         log.error("Invalid value for height. Using defaults.")
  411.                         bry = None
  412.                 else:
  413.                     log.error("Cannot calculate bry since tly is invalid. Using defaults.")
  414.                     bry = None
  415.  
  416.         elif o == '--size':
  417.             size = a.strip().lower()
  418.             if size in PAGE_SIZES:
  419.                 brx, bry, size_desc, page_units = PAGE_SIZES[size]
  420.                 tlx, tly = 0, 0
  421.                 page_size = size
  422.             else:
  423.                 log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys()))
  424.                 log.error("Using defaults.")
  425.  
  426.         elif o in ('-o', '--output', '-f', '--file'):
  427.             output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip())))
  428.  
  429.             try:
  430.                 ext = os.path.splitext(output)[1]
  431.             except IndexError:
  432.                 log.error("Invalid filename extension.")
  433.                 output = ''
  434.                 if 'file' in dest:
  435.                     dest.remove('file')
  436.             else:
  437.                 if ext.lower() not in ('.jpg', '.png'):
  438.                     log.error("Only JPG (.jpg) and PNG (.png) output files are supported.")
  439.                     output = ''
  440.                     if 'file' in dest:
  441.                         dest.remove('file')
  442.                 else:
  443.                     if os.path.exists(output):
  444.                         log.warn("Output file '%s' exists. File will be overwritten." % output)
  445.  
  446.                     if 'file' not in dest:
  447.                         dest.append('file')
  448.  
  449.         elif o in ('-s', '--dest', '--destination'):
  450.             a = a.strip().lower().split(',')
  451.             for aa in a:
  452.                 aa = aa.strip()
  453.                 if aa in ('file', 'fax', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \
  454.                     and aa not in dest:
  455.                     if aa == 'print': aa = 'printer'
  456.                     dest.append(aa)
  457.  
  458.         elif o in ('-v', '--viewer'):
  459.             a = a.strip()
  460.             b = utils.which(a)
  461.             if not b:
  462.                 log.error("Viewer application not found.")
  463.             else:
  464.                 viewer = os.path.join(b, a)
  465.                 if 'viewer' not in dest:
  466.                     dest.append('viewer')
  467.  
  468.         elif o in ('-e', '--editor'):
  469.             a = a.strip()
  470.             b = utils.which(a)
  471.             if not b:
  472.                 log.error("Editor application not found.")
  473.             else:
  474.                 editor = os.path.join(b, a)
  475.                 if 'editor' not in dest:
  476.                     dest.append('editor')
  477.  
  478.         elif o == '--pdf':
  479.             a = a.strip()
  480.             b = utils.which(a)
  481.             if not b:
  482.                 log.error("PDF viewer application not found.")
  483.             else:
  484.                 pdf_viewer = os.path.join(b, a)
  485.                 if 'pdf' not in dest:
  486.                     dest.append('pdf')
  487.  
  488.  
  489.         elif o in ('--email-to', '--to'):
  490.             email_to = a.split(',')
  491.             if 'email' not in dest:
  492.                 dest.append('email')
  493.  
  494.         elif o in ('--email-from', '--from'):
  495.             email_from = a
  496.             if 'email' not in dest:
  497.                 dest.append('email')
  498.  
  499.         elif o in ('--email-subject', '--subject', '--about'):
  500.             email_subject = a
  501.             if 'email' not in dest:
  502.                 dest.append('email')
  503.  
  504.         elif o in ('--email-note', '--email-msg', '--msg', '--message', '--note', '--notes'):
  505.             email_note = a
  506.             if 'email' not in dest:
  507.                 dest.append('email')
  508.  
  509.         elif o == '--resize':
  510.             a = a.replace("%", "")
  511.             try:
  512.                 resize = int(a)
  513.             except ValueError:
  514.                 resize = 100
  515.                 log.error("Invalid resize value. Using default of 100%.")
  516.  
  517.         elif o in ('-b', '--brightness'):
  518.             pass
  519.  
  520.         elif o in ('-c', '--contrast'):
  521.             try:
  522.                 contrast = int(a.strip())
  523.             except ValueError:
  524.                 log.error("Invalid contrast value. Using default of 100.")
  525.                 contrast = 100
  526.  
  527.         elif o == '--adf':
  528.             adf = True
  529.             output_type = 'pdf'
  530.  
  531.  
  532.     if printer_name is not None and \
  533.         device.getDeviceURIByPrinterName(printer_name) is not None and \
  534.         'printer' not in dest:
  535.  
  536.         dest.append('printer')
  537.  
  538.     if 'fax' in dest and 'file' not in dest:
  539.         log.error("Fax destination not implemented. Adding 'file' destination. Use output file to fax.")
  540.         dest.append('file')
  541.  
  542.     if not dest:
  543.         log.warn("No destinations specified. Adding 'file' destination by default.")
  544.         dest.append('file')
  545.  
  546.     if 'file' in dest and not output:
  547.         log.warn("File destination enabled with no output file specified.")
  548.  
  549.         if adf:
  550.             log.info("Setting output format to PDF for ADF mode.")
  551.             output = utils.createSequencedFilename("hpscan", ".pdf")
  552.             output_type = 'pdf'
  553.         else:
  554.             if scan_mode == 'gray':
  555.                 log.info("Setting output format to PNG for greyscale mode.")
  556.                 output = utils.createSequencedFilename("hpscan", ".png")
  557.                 output_type = 'png'
  558.             else:
  559.                 log.info("Setting output format to JPEG for color/lineart mode.")
  560.                 output = utils.createSequencedFilename("hpscan", ".jpg")
  561.                 output_type = 'jpeg'
  562.  
  563.         log.warn("Defaulting to '%s'." % output)
  564.  
  565.     else:
  566.         try:
  567.             output_type = os.path.splitext(output)[1].lower()[1:]
  568.             if output_type == 'jpg':
  569.                 output_type = 'jpeg'
  570.         except IndexError:
  571.             output_type = ''
  572.  
  573.     if output_type and output_type not in ('jpeg', 'png', 'pdf'):
  574.         log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.")
  575.         sys.exit(1)
  576.  
  577.     if adf and output_type and output_type != 'pdf':
  578.         log.error("ADF scans must be saved in PDF file format.")
  579.         sys.exit(1)
  580.  
  581.     if 'email' in dest and (not email_from or not email_to):
  582.         log.error("Email specified, but email to and/or email from address(es) were not specified.")
  583.         log.error("Disabling 'email' destination.")
  584.         dest.remove("email")
  585.  
  586.     if page_size:
  587.         units = 'mm'
  588.  
  589.     if units == 'in':
  590.         if tlx is not None: tlx = tlx * 25.4
  591.         if tly is not None: tly = tly * 25.4
  592.         if brx is not None: brx = brx * 25.4
  593.         if bry is not None: bry = bry * 25.4
  594.  
  595.     elif units == 'cm':
  596.         if tlx is not None: tlx = tlx * 10.0
  597.         if tly is not None: tly = tly * 10.0
  598.         if brx is not None: brx = brx * 10.0
  599.         if bry is not None: bry = bry * 10.0
  600.  
  601.     elif units == 'pt':
  602.         if tlx is not None: tlx = tlx * 0.3528
  603.         if tly is not None: tly = tly * 0.3528
  604.         if brx is not None: brx = brx * 0.3528
  605.         if bry is not None: bry = bry * 0.3528
  606.  
  607.     elif units == 'px':
  608.         log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res)
  609.         if tlx is not None: tlx = tlx / res * 25.4
  610.         if tly is not None: tly = tly / res * 25.4
  611.         if brx is not None: brx = brx / res * 25.4
  612.         if bry is not None: bry = bry / res * 25.4
  613.  
  614.     if tlx is not None and brx is not None and tlx >= brx:
  615.         log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx))
  616.         tlx = brx = None
  617.  
  618.     if tly is not None and bry is not None and tly >= bry:
  619.         log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry))
  620.         tly = bry = None
  621.  
  622.     if not prop.scan_build:
  623.         log.error("Scanning disabled in build. Exiting")
  624.         sys.exit(1)
  625.  
  626.     if mode == GUI_MODE:
  627.         log.error("GUI mode is not implemented yet. Please use -n. Refer to 'hp-scan -h' for help.")
  628.         sys.exit(1)
  629.  
  630.  
  631.     if 1:
  632.         #else: # NON_INTERACTIVE_MODE
  633.         import Queue
  634.         from scan import sane
  635.         import scanext
  636.         import cStringIO
  637.  
  638.         try:
  639.             import subprocess
  640.         except ImportError:
  641.             # Pre-2.4 Python
  642.             from base import subproc as subprocess
  643.  
  644.         try:
  645.             import Image
  646.         except ImportError:
  647.             log.error("%s requires the Python Imaging Library (PIL). Exiting." % __mod__)
  648.             sys.exit(1)
  649.  
  650.         sane.init()
  651.         devices = sane.getDevices()
  652.  
  653.         # Make sure SANE backend sees the device...
  654.         for d, mfg, mdl, t in devices:
  655.             if d == device_uri:
  656.                 break
  657.         else:
  658.             log.error("Unable to locate device %s using SANE backend hpaio:. Please check HPLIP installation." % device_uri)
  659.             sys.exit(1)
  660.  
  661.         log.info(log.bold("Using device %s" % device_uri))
  662.         log.info("Opening connection to device...")
  663.  
  664.         try:
  665.             device = sane.openDevice(device_uri)
  666.         except scanext.error, e:
  667.             sane.reportError(e)
  668.             sys.exit(1)
  669.  
  670.         tlx = device.getOptionObj('tl-x').limitAndSet(tlx)
  671.         tly = device.getOptionObj('tl-y').limitAndSet(tly)
  672.         brx = device.getOptionObj('br-x').limitAndSet(brx)
  673.         bry = device.getOptionObj('br-y').limitAndSet(bry)
  674.  
  675.         scan_area = (brx - tlx) * (bry - tly) # mm^2
  676.         scan_px = scan_area * res * res / 645.16 # res is in DPI
  677.  
  678.         if scan_mode == 'color':
  679.             scan_size = scan_px * 3 # 3 bytes/px
  680.         else:
  681.             scan_size = scan_px # 1 byte/px
  682.  
  683.         if scan_size > 52428800: # 50MB
  684.             if res > 600:
  685.                 log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.")
  686.             else:
  687.                 log.warn("The scan current parameters will cause very large files to be created.")
  688.  
  689.             log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.")
  690.             log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True))
  691.  
  692.         res = device.getOptionObj('resolution').limitAndSet(res)
  693.  
  694.         device.setOption('compression', scanner_compression)
  695.  
  696.         if brx - tlx <= 0.0 or bry - tly <= 0.0:
  697.             log.error("Invalid scan area (width or height is negative).")
  698.             sys.exit(1)
  699.  
  700.         log.info("")
  701.         log.info("Resolution: %ddpi" % res)
  702.         log.info("Mode: %s" % scan_mode)
  703.         log.info("Compression: %s" % scanner_compression)
  704.         log.info("Scan area (mm):")
  705.         log.info("  Top left (x,y): (%fmm, %fmm)" % (tlx, tly))
  706.         log.info("  Bottom right (x,y): (%fmm, %fmm)" % (brx, bry))
  707.         log.info("  Width: %fmm" % (brx - tlx))
  708.         log.info("  Height: %fmm" % (bry - tly))
  709.  
  710.         if page_size:
  711.             units = page_units # for display purposes only
  712.             log.info("Page size: %s" % size_desc)
  713.             if units != 'mm':
  714.                 log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units)
  715.  
  716.         if units == 'in':
  717.             log.info("Scan area (in):")
  718.             log.info("  Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4))
  719.             log.info("  Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4))
  720.             log.info("  Width: %fin" % ((brx - tlx)/25.4))
  721.             log.info("  Height: %fin" % ((bry - tly)/25.4))
  722.  
  723.         elif units == 'cm':
  724.             log.info("Scan area (cm):")
  725.             log.info("  Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0))
  726.             log.info("  Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0))
  727.             log.info("  Width: %fcm" % ((brx - tlx)/10.0))
  728.             log.info("  Height: %fcm" % ((bry - tly)/10.0))
  729.  
  730.         elif units == 'px':
  731.             log.info("Scan area (px @ %ddpi):" % res)
  732.             log.info("  Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4))
  733.             log.info("  Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4))
  734.             log.info("  Width: %fpx" % ((brx - tlx)*res/25.4))
  735.             log.info("  Height: %fpx" % ((bry - tly)*res/25.4))
  736.  
  737.         elif units == 'pt':
  738.             log.info("Scan area (pt):")
  739.             log.info("  Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528))
  740.             log.info("  Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528))
  741.             log.info("  Width: %fpt" % ((brx - tlx)/0.3528))
  742.             log.info("  Height: %fpt" % ((bry - tly)/0.3528))
  743.  
  744.         log.info("Destination(s): %s" % ', '.join(dest))
  745.  
  746.         if 'file' in dest:
  747.             log.info("Output file: %s" % output)
  748.  
  749.         update_queue = Queue.Queue()
  750.         event_queue = Queue.Queue()
  751.  
  752.         device.setOption("mode", scan_mode)
  753.         device.setOption("resolution", res)
  754.  
  755.         if adf:
  756.             try:
  757.                 device.setOption("source", "ADF")
  758.                 device.setOption("batch-scan", True)
  759.             except scanext.error:
  760.                 log.error("Failed to set ADF mode. Does this device support ADF? Disabling ADF mode.")
  761.                 adf = False
  762.  
  763.         if not adf:
  764.             try:
  765.                 #device.setOption("source", "Auto")
  766.                 device.setOption("batch-scan", False)
  767.             except scanext.error:
  768.                 log.debug("Error setting source or batch-scan option (this is probably OK).")
  769.  
  770.         log.info("\nWarming up...")
  771.  
  772.         no_docs = False
  773.         page = 1
  774.         adf_page_files = []
  775.         #adf_pages = []
  776.  
  777.         cleanup_spinner()
  778.         log.info("")
  779.  
  780.         try:
  781.             while True:
  782.                 if adf:
  783.                     log.info("\nPage %d: Scanning..." % page)
  784.                 else:
  785.                     log.info("\nScanning...")
  786.  
  787.                 bytes_read = 0
  788.  
  789.                 try:
  790.                     try:
  791.                         ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue)
  792.                         # Note: On some scanners (Marvell) expected_bytes will be < 0 (if lines == -1)
  793.                         log.debug("expected_bytes = %d" % expected_bytes)
  794.                     except scanext.error, e:
  795.                         #log.error(e)
  796.                         sane.reportError(e)
  797.                         sys.exit(1)
  798.                     except KeyboardInterrupt:
  799.                         log.error("Aborted.")
  800.                         device.cancelScan()
  801.                         sys.exit(1)
  802.  
  803.                     if adf and status == scanext.SANE_STATUS_NO_DOCS:
  804.                         if page-1 == 0:
  805.                             log.error("No document(s). Please load documents and try again.")
  806.                             sys.exit(0)
  807.                         else:
  808.                             log.info("Out of documents. Scanned %d pages total." % (page-1))
  809.                             no_docs = True
  810.                             break
  811.  
  812.                     if expected_bytes > 0:
  813.                         if adf:
  814.                             log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes))
  815.                         else:
  816.                             log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes))
  817.  
  818.                     device.waitForScanActive()
  819.  
  820.                     pm = tui.ProgressMeter("Reading data:")
  821.  
  822.                     while device.isScanActive():
  823.                         while update_queue.qsize():
  824.                             try:
  825.                                 status, bytes_read = update_queue.get(0)
  826.  
  827.                                 #if log.get_level() >= log.LOG_LEVEL_INFO:
  828.                                 if not log.is_debug():
  829.                                     if expected_bytes > 0:
  830.                                         pm.update(int(100*bytes_read/expected_bytes),
  831.                                             utils.format_bytes(bytes_read))
  832.                                     else:
  833.                                         pm.update(0,
  834.                                             utils.format_bytes(bytes_read))
  835.  
  836.  
  837.     ##                            if status == scanext.SANE_STATUS_EOF:
  838.     ##                                log.debug("EOF")
  839.     ##                            elif status == scanext.SANE_STATUS_NO_DOCS:
  840.     ##                                log.debug("NO DOCS")
  841.     ##                                no_docs = True
  842.                                 #elif status != scanext.SANE_STATUS_GOOD:
  843.                                 if status != scanext.SANE_STATUS_GOOD:
  844.                                     sane.reportError(e)
  845.                                     #log.error("SANE error %d during readScan()" % status)
  846.                                     sys.exit(1)
  847.  
  848.                             except Queue.Empty:
  849.                                 break
  850.  
  851.  
  852.                         time.sleep(0.5)
  853.  
  854.                 except KeyboardInterrupt:
  855.                     log.error("Aborted.")
  856.                     device.cancelScan()
  857.                     sys.exit(1)
  858.  
  859.                 # Make sure queue is cleared out...
  860.                 while update_queue.qsize():
  861.                     status, bytes_read = update_queue.get(0)
  862.  
  863.                     if not log.is_debug():
  864.                         if expected_bytes > 0:
  865.                             pm.update(int(100*bytes_read/expected_bytes),
  866.                                 utils.format_bytes(bytes_read))
  867.                         else:
  868.                             pm.update(0,
  869.                                 utils.format_bytes(bytes_read))
  870.  
  871.     ##                if status == scanext.SANE_STATUS_EOF:
  872.     ##                    log.debug("EOF")
  873.     ##                elif status == scanext.SANE_STATUS_NO_DOCS:
  874.     ##                    log.debug("NO DOCS")
  875.     ##                    no_docs = True
  876.  
  877. #                    if status != scanext.SANE_STATUS_GOOD:
  878. #                        log.error("SANE error %d during queue clear" % status)
  879. #                        sys.exit(1)
  880.  
  881.                 log.info("")
  882.  
  883.                 if bytes_read:
  884.                     log.info("Read %s from scanner." % utils.format_bytes(bytes_read))
  885.  
  886.                     buffer, format, format_name, pixels_per_line, \
  887.                         lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan()
  888.  
  889.                     log.debug("PPL=%d lines=%d depth=%d BPL=%d pad=%d total=%d" %
  890.                         (pixels_per_line, lines, depth, bytes_per_line, pad_bytes, total_read))
  891.  
  892.                     if lines == -1:
  893.                         lines = int(total_read / bytes_per_line)
  894.  
  895.                     if scan_mode in ('color', 'gray'):
  896.                         try:
  897.                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  898.                                 'raw', 'RGBA', 0, 1)
  899.                         except ValueError:
  900.                             log.error("Did not read enough data from scanner (I/O Error?)")
  901.                             sys.exit(1)
  902.  
  903.     ##                elif scan_mode == 'gray':
  904.     ##                    im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  905.     ##                        'raw', 'RGBA', 0, 1).convert('P')
  906.  
  907.                     elif scan_mode == 'lineart':
  908.                         try:
  909.                             im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(),
  910.                                 'raw', 'RGBA', 0, 1).convert('L')
  911.                         except ValueError:
  912.                             log.error("Did not read enough data from scanner (I/O Error?)")
  913.                             sys.exit(1)
  914.  
  915.                     if adf:
  916.                         temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png")
  917.                         adf_page_files.append(temp_output)
  918.                         im.save(temp_output)
  919.                         log.debug("Saved page %d to file %s" % (page, temp_output))
  920.                         #adf_pages.append(im)
  921.                 else:
  922.                     log.error("No data read.")
  923.                     sys.exit(1)
  924.  
  925.                 if not adf or (adf and no_docs):
  926.                     break
  927.  
  928.                 page += 1
  929.  
  930.         finally:
  931.             log.info("Closing device.")
  932.             device.cancelScan()
  933.             #print "0"
  934.             #device.freeScan()
  935.             #sane.deInit()
  936.  
  937.         #if no_docs:
  938.         #    sys.exit(0)
  939.  
  940.         #print "1"
  941.         if adf:
  942.             try:
  943.                 from reportlab.pdfgen import canvas
  944.             except ImportError:
  945.                 log.error("PDF output requires ReportLab.")
  946.                 sys.exit(1)
  947.             #print "2"
  948.             #print canvas
  949.             #print canvas.Canvas
  950.  
  951.             tlx_max = device.getOptionObj('tl-x').constraint[1]
  952.             bry_max = device.getOptionObj('br-y').constraint[1]
  953.  
  954.             if not output:
  955.                 output = utils.createSequencedFilename("hpscan", ".pdf")
  956.  
  957.             c = canvas.Canvas(output, (tlx_max/0.3528, bry_max/0.3528))
  958.             #print c
  959.  
  960.             #for p in adf_page_files:
  961.             for p in adf_page_files:
  962.                 log.info("Processing page %s..." % p)
  963.  
  964.                 image = Image.open(p)
  965.                 #print image
  966.  
  967.                 try:
  968.                     c.drawInlineImage(image, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)),
  969.  
  970.                     #c.drawInlineImage(image, 0, bry/0.3528,
  971.                         width=None,height=None)
  972.                 except NameError:
  973.                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
  974.                     sys.exit(1)
  975.  
  976.                 c.showPage()
  977.  
  978.             log.info("Saving to file %s" % output)
  979.             c.save()
  980.             log.info("Viewing PDF file in %s" % pdf_viewer)
  981.             os.system("%s %s &" % (pdf_viewer, output))
  982.  
  983.  
  984.             sys.exit(0)
  985.  
  986.         if resize != 100:
  987.             if resize < 1 or resize > 400:
  988.                 log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.")
  989.                 log.error("Using resize value of 100%.")
  990.             else:
  991.                 new_w = pixels_per_line * resize / 100
  992.                 new_h = lines * resize / 100
  993.                 log.info("Resizing from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h))
  994.                 im = im.resize((new_w, new_h), Image.ANTIALIAS)
  995.  
  996.         file_saved = False
  997.         if 'file' in dest:
  998.             log.info("\nOutputting to destination 'file':")
  999.             log.info("Saving to file %s" % output)
  1000.  
  1001.             try:
  1002.                 im.save(output)
  1003.             except IOError, e:
  1004.                 log.error("Error saving file: %s (I/O)" % e)
  1005.                 try:
  1006.                     os.remove(output)
  1007.                 except OSError:
  1008.                     pass
  1009.                 sys.exit(1)
  1010.             except ValueError, e:
  1011.                 log.error("Error saving file: %s (PIL)" % e)
  1012.                 try:
  1013.                     os.remove(output)
  1014.                 except OSError:
  1015.                     pass
  1016.                 sys.exit(1)
  1017.  
  1018.             file_saved = True
  1019.             dest.remove("file")
  1020.  
  1021.         temp_saved = False
  1022.         if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \
  1023.             and not file_saved:
  1024.  
  1025.             output_fd, output = utils.make_temp_file(suffix='.png')
  1026.             try:
  1027.                 im.save(output)
  1028.             except IOError, e:
  1029.                 log.error("Error saving temporary file: %s" % e)
  1030.  
  1031.                 try:
  1032.                     os.remove(output)
  1033.                 except OSError:
  1034.                     pass
  1035.  
  1036.                 sys.exit(1)
  1037.  
  1038.             os.close(output_fd)
  1039.             temp_saved = True
  1040.  
  1041.         for d in dest:
  1042.             log.info("\nSending to destination '%s':" % d)
  1043.  
  1044.             if d == 'fax':
  1045.                 log.error("fax: Not implemented yet.")
  1046.  
  1047.             elif d == 'pdf':
  1048.                 try:
  1049.                     from reportlab.pdfgen import canvas
  1050.                 except ImportError:
  1051.                     log.error("PDF output requires ReportLab.")
  1052.                     continue
  1053.  
  1054.                 tlx_max = device.getOptionObj('tl-x').constraint[1]
  1055.                 bry_max = device.getOptionObj('br-y').constraint[1]
  1056.  
  1057.                 pdf_output = utils.createSequencedFilename("hpscan", ".pdf")
  1058.                 c = canvas.Canvas(pdf_output, (tlx_max/0.3528, bry_max/0.3528))
  1059.  
  1060.                 try:
  1061.                     c.drawInlineImage(im, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)),
  1062.                         width=None,height=None)
  1063.                 except NameError:
  1064.                     log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.")
  1065.                     continue
  1066.  
  1067.                 c.showPage()
  1068.                 log.info("Saving to file %s" % pdf_output)
  1069.                 c.save()
  1070.                 log.info("Viewing PDF file in %s" % pdf_viewer)
  1071.                 os.system("%s %s &" % (pdf_viewer, pdf_output))
  1072.  
  1073.             elif d == 'printer':
  1074.                 hp_print = utils.which("hp-print")
  1075.                 if hp_print:
  1076.                     cmd = 'hp-print %s &' % output
  1077.                 else:
  1078.                     cmd = "python ./print.py %s &" % output
  1079.  
  1080.                 os.system(cmd)
  1081.  
  1082.             elif d == 'email':
  1083.                 try:
  1084.                     from email.mime.image import MIMEImage
  1085.                     from email.mime.multipart import MIMEMultipart
  1086.                     from email.mime.text import MIMEText
  1087.                 except ImportError:
  1088.                     try:
  1089.                         from email.MIMEImage import MIMEImage
  1090.                         from email.MIMEMultipart import MIMEMultipart
  1091.                         from email.MIMEText import MIMEText
  1092.                     except ImportError:
  1093.                         log.error("hp-scan email destination requires Python 2.2+.")
  1094.                         continue
  1095.  
  1096.                 msg = MIMEMultipart()
  1097.                 msg['Subject'] = email_subject
  1098.                 msg['From'] = email_from
  1099.                 msg['To'] = ','.join(email_to)
  1100.                 msg.preamble = 'Scanned using hp-scan'
  1101.  
  1102.                 if email_note:
  1103.                     txt = MIMEText(email_note)
  1104.                     msg.attach(txt)
  1105.  
  1106.                 if file_saved:
  1107.                     txt = MIMEText("attached: %s: %dx%d %s PNG image." %
  1108.                         (os.path.basename(output), pixels_per_line, lines, scan_mode))
  1109.                 else:
  1110.                     txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode))
  1111.  
  1112.                 msg.attach(txt)
  1113.  
  1114.                 fp = open(output, 'r')
  1115.                 img = MIMEImage(fp.read())
  1116.                 fp.close()
  1117.  
  1118.                 if file_saved:
  1119.                     img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output))
  1120.  
  1121.                 msg.attach(img)
  1122.  
  1123.                 sendmail = utils.which("sendmail")
  1124.  
  1125.                 if sendmail:
  1126.                     sendmail = os.path.join(sendmail, 'sendmail')
  1127.                     cmd = [sendmail,'-t','-r',email_from]
  1128.  
  1129.                     log.debug(repr(cmd))
  1130.                     err = None
  1131.                     try:
  1132.                         sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  1133.                         std_out, std_err = sp.communicate(msg.as_string())
  1134.                         if std_err != '':
  1135.                             err = std_err
  1136.                     except OSError, e:
  1137.                         err = str(e)
  1138.                     cleanup_spinner()
  1139.  
  1140.                     if err:
  1141.                         log.error(repr(err))
  1142.  
  1143.                 else:
  1144.                     log.error("Mail send failed. 'sendmail' not found.")
  1145.  
  1146.             elif d == 'viewer':
  1147.                 if viewer:
  1148.                     log.info("Viewing file in %s" % viewer)
  1149.                     os.system("%s %s &" % (viewer, output))
  1150.                 else:
  1151.                     log.error("Viewer not found.")
  1152.  
  1153.             elif d == 'editor':
  1154.                 if editor:
  1155.                     log.info("Editing file in %s" % editor)
  1156.                     os.system("%s %s &" % (editor, output))
  1157.                 else:
  1158.                     log.error("Editor not found.")
  1159.  
  1160.         device.freeScan()
  1161.         sane.deInit()
  1162.  
  1163.  
  1164. except KeyboardInterrupt:
  1165.     log.error("User exit")
  1166.  
  1167. log.info("")
  1168. log.info("Done.")
  1169.  
  1170.